package org.lee.web.service;

import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.solon.annotation.Db;
import org.lee.common.constant.CacheConstants;
import org.lee.common.constant.Constants;
import org.lee.common.constant.UserConstants;
import org.lee.common.core.dto.RoleDTO;
import org.lee.common.core.enums.UserStatusEnum;
import org.lee.common.core.enums.UserTypeEnum;
import org.lee.common.core.service.redis.RedisService;
import org.lee.common.core.vo.request.LoginBody;
import org.lee.common.core.vo.request.LoginUser;
import org.lee.common.core.vo.response.AjaxResult;
import org.lee.common.core.vo.system.SysUserVo;
import org.lee.common.exception.ServiceException;
import org.lee.common.exception.auth.*;
import org.lee.common.util.LoginHelper;
import org.lee.common.util.ip.IpUtils;
import org.lee.framework.manager.AsyncFactory;
import org.lee.framework.manager.AsyncManager;
import org.lee.system.mapper.SysUserMapper;
import org.lee.system.service.SysConfigService;
import org.lee.system.service.SysPermissionService;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.i18n.I18nUtil;

import java.time.Duration;
import java.util.List;

@Slf4j
@Component
public class AuthService {

    @Db("lee")
    SysUserMapper userMapper;

    @Inject("${lee.captcha-enabled}")
    private boolean captchaEnabled;

    @Inject
    private RedisService redisService;

    @Inject
    private SysPermissionService permissionService;

    @Inject
    private SysConfigService configService;

    @Inject("${user.password.max-retry-count}")
    private int maxRetryCount;

    @Inject("${user.password.lock-time}")
    private int lockTime;


    public String login(LoginBody body) {
        // 登录前置校验
        final String username = body.getUsername();
        final String password = body.getPassword();
        final String uuid = body.getUuid();
        final String code = body.getCode();
        // 校验验证码
        verifyCode(uuid, code, username);
        // 校验用户名或密码参数
        verifyUsernamePasswdParam(username, password);
        // 获取用户及密码校验
        SysUserVo user = checkUsernamePasswdFromDb(username, password);
        LoginUser loginUser = buildLoginUser(user);
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
        LoginHelper.login(loginUser, new SaLoginModel());
        // 记录登录信息
        AsyncManager.getInstance().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, I18nUtil.getMessage("user.login.success")));
        return StpUtil.getTokenValue();
    }

    private SysUserVo checkUsernamePasswdFromDb(String username, String password) {
        SysUserVo user = userMapper.selectUserByUserName(username);
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户：{} 不存在.", username);
            throw new ServiceException(I18nUtil.getMessage("user.not.exists"));
        } else if (UserStatusEnum.DELETED.getCode().equals(user.getDelFlag())) {
            log.info("登录用户：{} 已被删除.", username);
            throw new ServiceException(I18nUtil.getMessage("user.password.delete"));
        } else if (UserStatusEnum.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户：{} 已被停用.", username);
            throw new ServiceException(I18nUtil.getMessage("user.blocked"));
        }
        String priKey = configService.selectConfigByKey("sys.login.sm2PriKey");
        SM2 sm2 = SmUtil.sm2(priKey, null);
        String hashStr = SmUtil.sm3(sm2.decryptStr(password, KeyType.PrivateKey));
        Integer retryCount = redisService.getCacheObject(getCacheKey(username));
        if (retryCount == null) {
            retryCount = 0;
        }
        if (retryCount >= maxRetryCount) {
            throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
        }
        if (!CharSequenceUtil.equalsIgnoreCase(hashStr, user.getPassword())) {
            retryCount = retryCount + 1;
            redisService.setCacheObject(getCacheKey(username), retryCount, Duration.ofMinutes(lockTime));
            throw new UserPasswordNotMatchException();
        } else {
            clearLoginRecordCache(username);
        }
        // 设置为空
        user.setPassword(null);
        return user;
    }

    private void clearLoginRecordCache(String loginName) {
        if (Boolean.TRUE.equals(redisService.hasKey(getCacheKey(loginName)))) {
            redisService.deleteObject(getCacheKey(loginName));
        }
    }

    /**
     * 登录账户密码错误次数缓存键名
     *
     * @param username 用户名
     * @return 缓存键key
     */
    private String getCacheKey(String username) {
        return CacheConstants.PWD_ERR_CNT_KEY + username;
    }

    /**
     * 校验用户名密码
     *
     * @param username username
     * @param password password
     */
    private void verifyUsernamePasswdParam(String username, String password) {
        if (CharSequenceUtil.isEmpty(username) || CharSequenceUtil.isEmpty(password)) {
            AsyncManager.getInstance().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, I18nUtil.getMessage("not.null")));
            throw new UserNotExistsException();
        }
        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
            AsyncManager.getInstance().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, I18nUtil.getMessage("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
            AsyncManager.getInstance().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, I18nUtil.getMessage("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // IP黑名单校验
        String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
            AsyncManager.getInstance().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, I18nUtil.getMessage("login.blocked")));
            throw new BlackListException();
        }
    }

    /**
     * 验证码校验
     *
     * @param uuid     uuid
     * @param code     code
     * @param username username
     */
    private void verifyCode(String uuid, String code, String username) {
        if (captchaEnabled) {
            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
            String captcha = redisService.getCacheObject(verifyKey);
            redisService.deleteObject(verifyKey);
            if (captcha == null) {
                AsyncManager.getInstance().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, I18nUtil.getMessage("user.jcaptcha.expire")));
                throw new CaptchaExpireException();
            }
            if (!CharSequenceUtil.equalsIgnoreCase(code, captcha)) {
                AsyncManager.getInstance().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, I18nUtil.getMessage("user.jcaptcha.error")));
                throw new CaptchaException();
            }
        }
    }


    /**
     * 构建登录用户
     */
    public LoginUser buildLoginUser(SysUserVo user) {
        LoginUser loginUser = new LoginUser();
        loginUser.setUser(user);
        loginUser.setUserType(UserTypeEnum.PC);
        loginUser.setUserId(user.getUserId());
        loginUser.setDeptId(user.getDeptId());
        loginUser.setUsername(user.getUserName());
        loginUser.setNickname(user.getNickName());
        loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
        loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
        loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
        List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
        loginUser.setRoles(roles);
        return loginUser;
    }

    public AjaxResult getInfo() {
        AjaxResult ajax = AjaxResult.success();
        LoginUser loginUser = LoginHelper.getLoginUser();
        ajax.put("user", loginUser.getUser());
        ajax.put("roles", loginUser.getRolePermission());
        ajax.put("permissions", loginUser.getMenuPermission());
        return ajax;
    }
}
